﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using AutoMapper;
using AutoMapper.Internal;
using Inovout.Data.Domain;
using Inovout.Data.Repository;
using Inovout.Descriptor;

namespace Inovout.Web.Http
{
    public class MergedParameterControllerActionInvoker : ApiControllerActionInvoker
    {
        private static readonly ConcurrentBag<Type> mappedTypeBag = new ConcurrentBag<Type>();
        public override Task<System.Net.Http.HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            var keys = actionContext.ActionArguments.Keys.ToArray();
            var values = actionContext.ActionArguments.Values.ToArray();
            for (int i = 0; i < actionContext.ActionArguments.Count; i++)
            {
                var key = keys[i];
                var value = values[i];
                actionContext.ActionArguments[key] = MergeValue(actionContext.Request,
                   actionContext.ActionDescriptor.GetParameters()[i].ParameterType, value);
            }
            return base.InvokeActionAsync(actionContext, cancellationToken);
        }
        private object MergeValue(HttpRequestMessage request, Type type, object value)
        {
            if (value == null)
            {
                return value;
            }
            Type modelType = value.GetType();
            if (!typeof(DataObject).IsAssignableFrom(modelType))
            {
                return value;
            }
            var propertyDescriptor = TypeDescriptor.Get(type).Properties.SingleOrDefault(p => p.HasAttribute<DataKeyAttribute>())??
                                     TypeDescriptor.Get(modelType).Properties.SingleOrDefault(p => p.HasAttribute<DataKeyAttribute>());
            if (propertyDescriptor == null)
            {
                return value;
            }
      
            QueryCriteria queryCriteria = new QueryCriteria();
            queryCriteria.Filters.Add(new FilterCriteria(propertyDescriptor.Name, FilterOperation.Eq, propertyDescriptor.GetValue(value).ToString()));

            Type repositoryType = typeof(IRepository<>);
            repositoryType = repositoryType.MakeGenericType(modelType);
            IRepository repository = (IRepository)request.GetDependencyScope().GetService(repositoryType);
            var dataObjects = repository.Query(queryCriteria).Cast<DataObject>().ToArray();
            if (dataObjects.Length > 1)
            {
                throw new Exception("DataKey Property Value 不唯一");
            }
            var dataObject = dataObjects.Length == 0 ? null : dataObjects[0];
            if (dataObject == null)
            {
                return value;
            }

            if (!mappedTypeBag.Contains(modelType))
            {
                IMappingExpression mapExpression = ((IMapperConfiguration)mapperConfiguration).CreateMap(modelType, modelType);
                var propertyDescriptors = TypeDescriptor.Get(modelType).Properties.Except(TypeDescriptor.Get(type).Properties, propertyDescriptorComparer);

                foreach (var property in propertyDescriptors)
                {
                    mapExpression.TypeMap.FindOrCreatePropertyMapFor(new PropertyAccessor(property.PropertyInfo)).Ignore();
                }
                mappedTypeBag.Add(modelType);
            }


            mapper.Map(value, dataObject);
            return dataObject;
        }
        private static readonly MapperConfiguration mapperConfiguration = new MapperConfiguration(cfg => cfg.CreateMap<DataObject, DataObject>());
        private static readonly IMapper mapper = mapperConfiguration.CreateMapper();
        private static readonly PropertyDescriptorComparer propertyDescriptorComparer = new PropertyDescriptorComparer();
        private class PropertyDescriptorComparer : IEqualityComparer<PropertyDescriptor>
        {
            // Products are equal if their names and product numbers are equal.
            public bool Equals(PropertyDescriptor thisPropertyDescriptor, PropertyDescriptor thatRropertyDescriptor)
            {

                if (Object.ReferenceEquals(thisPropertyDescriptor, thatRropertyDescriptor)) return true;
                if (Object.ReferenceEquals(thisPropertyDescriptor, null) || Object.ReferenceEquals(thatRropertyDescriptor, null))
                    return false;

                return thisPropertyDescriptor.Name == thatRropertyDescriptor.Name;
            }


            public int GetHashCode(PropertyDescriptor obj)
            {
                return obj.Name.GetHashCode();
            }
        }
    }

}